import org.ini4j.Ini;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.HashSet;

public class CTRLoginJFrame extends JFrame {
    DatagramSocket socketForServer = null;//在这里声明 来让后面的内部类也能调用
    public CTRLoginJFrame() {


        //初始化窗体
        this.setTitle("mCAU聊天室 登录 "+"      软件版本："+Global.ver);
        this.setSize(600,600);
        this.setLocationRelativeTo(null);



        //初始化窗体的组件 主要参考：https://blog.csdn.net/2301_79263365/article/details/137753530
        //面板和标签
        JPanel panel = new JPanel(new GridLayout(5, 2));//网格布局 5行2列
        JLabel labelName = new JLabel("昵称（与读取的聊天记录有关）", JLabel.CENTER);
        JLabel labelIp = new JLabel("输入IP",JLabel.CENTER);
        JLabel labelPort = new JLabel("输入端口",JLabel.CENTER);

        //文本框
        JTextField textFieldName = new JTextField(20);
        JTextField textFieldIp = new JTextField(20);
        JTextField textFieldPort = new JTextField(20);

        //按钮
        JButton buttonLoginClientOnly = new JButton("仅启动客户端(必须输入IP和端口)");
        JButton buttonLoginWithServer = new JButton("启动客户端与服务端(无需输入IP和端口)");
        JButton buttonRead = new JButton("从配置文件中读取IP和端口");
        JButton buttonSave = new JButton("在配置文件中保存当前输入的IP和端口");

        //实现 仅启动客户端(必须输入IP和端口) 按钮的功能
        buttonLoginClientOnly.addMouseListener(new MouseAdapter() {

            public void mouseClicked(MouseEvent e) {


                InetAddress address=null;
                int port=0;
                String name;


                try{
                    address=InetAddress.getByName(textFieldIp.getText());
                    port=Integer.parseInt(textFieldPort.getText());
                    name=textFieldName.getText();

                    FrameClose();//关闭登陆窗体
                    new CTRMainJFrame(address,port,name);//打开主窗体 也就是聊天窗体


                }catch(Exception ex)
                {
                    JumpWin("IP或端口输入有误 请重试");
                }



            }
        });

        //实现 启动客户端与服务端(无需输入IP和端口) 按钮的功能
        buttonLoginWithServer.addMouseListener(new MouseAdapter() {

            public void mouseClicked(MouseEvent e) {

                //一个新的线程 专门用来在后台运行服务端 没有任何的界面显示
                Thread threadServer = new Thread(new Runnable(){
                    @Override
                    public void run(){

                        //初始化Socket
                        try {
                            socketForServer = new DatagramSocket();
                        } catch (SocketException ex) {
                            throw new RuntimeException(ex);
                        }

                        //初始化传输的载体 byte数组
                        byte[] buf = new byte[1024*30]; //本来用的是1024*64 但是会发不出去

                        //初始化packet
                        DatagramPacket packet = new DatagramPacket(buf, buf.length);

                        //用来存储各个客户端的ip和“接收端口”
                        //为了避免发出数据与接收数据的端口冲突（实测发现会导致数据错乱 可能是因为客户端可能同时发送和接收数据导致 而服务端不会同时发送和接受）
                        //这里采用的方法是在客户端处把发出的端口与接收的端口分开 分别用两个Socket
                        //下面的HashSet是用来存储ip和接收端口的 以Pair的方式存储
                        //Pair是我模仿C++中的Pair编写的类

                        HashSet<Pair> hs=new HashSet<Pair>();//用来存储ip和端口

                        //mzn

                        while(true){
                            //初始化用于接收的Socket
                            try {
                                socketForServer.receive(packet);
                            } catch (IOException ex) {
                                throw new RuntimeException(ex);
                            }

                            //
                            String rec = new String(packet.getData(), 0, packet.getLength(), StandardCharsets.UTF_8);

                            String msg=rec.substring(5);

                            //客户端的接收端口 这里采用的方法是在客户端发送的每条数据的前五个字符用来表示接收端口
                            int clientPort=Integer.parseInt(rec.substring(0,5));

                            //测试用的输出
                            System.out.println(rec);

                            //    System.out.println(packet.getAddress().getHostAddress());
                            //    System.out.println(packet.getPort());

                            //每次收到数据 都把客户端的ip和接收端口放入HashSet
                            hs.add(new Pair(packet.getAddress(),clientPort));

                            //把前5个字符 即客户端的端口去掉后 再发回给各个客户端
                            byte[] resendBuf=msg.getBytes(StandardCharsets.UTF_8);//注意用UTF-8 以支持中文

                            //循环遍历 给HashSet中存储的各个客户端(IP和接收端口)都把最新收到的一条消息发一遍
                            for(Pair p:hs){

                                DatagramPacket reSend = new DatagramPacket(resendBuf, resendBuf.length, p.first, p.second);
                                try {
                                    socketForServer.send(reSend);
                                } catch (IOException ex) {
                                    throw new RuntimeException(ex);
                                }
                            }
                        }
                    }
                });
                //启动该线程
                threadServer.start();

                //隐藏登陆界面 下面打开主窗体 即聊天窗体
                FrameClose();


                //打开主窗体
                try {
                    new CTRMainJFrame(InetAddress.getLocalHost(),socketForServer.getLocalPort(),textFieldName.getText());
                } catch (UnknownHostException ex) {
                    throw new RuntimeException(ex);
                }
                //mzn

                //发现如果用固定的端口很容易发生冲突 因此用分配的端口 在服务器启动后用一个弹窗把它显示出来
                try {
                    JOptionPane.showMessageDialog(null, "IP与端口:"+InetAddress.getLocalHost().getHostAddress()+":"+String.valueOf(socketForServer.getLocalPort()), "提示", JOptionPane.INFORMATION_MESSAGE);
                } catch (UnknownHostException ex) {
                    throw new RuntimeException(ex);
                }

            }
        });


        //用来调用 从配置文件读取ip和端口的功能 具体功能的实现在下面
        buttonRead.addMouseListener(new MouseAdapter() {

            public void mouseClicked(MouseEvent e) {
                Pair get=readFromIni();
                textFieldIp.setText(get.first.getHostAddress());
                textFieldPort.setText(String.valueOf(get.second));
            }
        });


        buttonSave.addMouseListener(new MouseAdapter() {

            public void mouseClicked(MouseEvent e) {
                try {
                    writeToIni(InetAddress.getByName(textFieldIp.getText()),Integer.parseInt(textFieldPort.getText()));
                } catch (UnknownHostException ex) {
                    throw new RuntimeException(ex);
                }

            }
        });

        //把创建好的组件依次添加到面板 再把面板添加到窗体

        panel.add(labelName);
        panel.add(textFieldName);
        panel.add(labelIp);
        panel.add(textFieldIp);
        panel.add(labelPort);
        panel.add(textFieldPort);
        panel.add(buttonLoginClientOnly);
        panel.add(buttonLoginWithServer);
        panel.add(buttonRead);
        panel.add(buttonSave);

        this.add(panel);

        //若关闭本窗体 直接结束程序 所以是3
        this.setDefaultCloseOperation(3);

        //窗体可见
        this.setVisible(true);



    }

    //下面的读写ini的功能 用了一个叫ini4j的包
    //读取Ini
    static public Pair readFromIni(){

        try {
            //构造ini对象
            Ini ini = new Ini(new File("config.ini"));

            //获取IP和端口
            String ip=ini.get("ipAndPort","ip");
            int port=Integer.parseInt(ini.get("ipAndPort","port"));

            InetAddress address=InetAddress.getByName(ip);//将ip转换成Inetaddress

            return new Pair(address,port);//为了和服务器中存储ip的方式一致 用Pair返回
        } catch (Exception e) {
            JumpWin("不存在配置文件\"config.ini\",请先用保存按钮创建");//失败就提示
            throw new RuntimeException(e);
        }
    }
    //修改Ini
    static public void writeToIni(InetAddress address, int port){
        try {

            File file=new File("config.ini");//创建File对象 来管理文件config.ini

            if(!file.exists())//如果不存在 就创建一个
            {
                file.createNewFile();
            }

            Ini ini = new Ini(file);//构造ini对象

            String ip=address.getHostAddress();//将InetAddress转换成字符串

            ini.put("ipAndPort","ip",ip);//修改ip
            ini.put("ipAndPort","port",String.valueOf(port));//修改端口

            ini.store();//保存
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    //关闭窗口
    void FrameClose()
    {
        this.dispose();//这个窗体在打开主窗体后需要隐藏
    }

    //弹窗
    static void JumpWin(String msg)
    {
        JOptionPane.showMessageDialog(null, msg, "提示", JOptionPane.INFORMATION_MESSAGE);

    }


}


